/*
    Copyright (c) 2009 Pavol Malosek a.k.a. malo

    This file is part of ComComLib.

    ComComLib is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.

    ComComLib is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <pthread.h>
#include <assert.h>
#include <unistd.h>
#include <poll.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <iostream>
#include <string.h>

#include <lcc/api.hpp>
#include <lcc/command.hpp>

lcc::api_t::api_t (void)
{

    //  Socketpair to communicate with I/O thread.
    int sv [2];
    int rc = socketpair (AF_UNIX, SOCK_STREAM, 0, sv);

    w = sv [0];
    r = sv [1];

    //  Fill the pollfd for poll.
    pollset.fd = r;
    pollset.events = POLLIN;

    //  Initialise loggers_mutex.
    rc = pthread_mutex_init (&loggers_mutex, NULL);
    assert (rc == 0);

    //  Create I/O thread.
    rc = pthread_create (&io_thread, NULL, io_routine, this);
    assert (rc == 0);
}

lcc::api_t::~api_t (void)
{

    //  Send terminate command to the io_thread.
    send_command (0, command_terminate);

    //  Wait for io_thread to terminate.
    int rc = pthread_join (io_thread, NULL);
    assert (rc == 0);

    //  Unallocate logger's devices.
    for (unsigned int i = 0; i < loggers.size (); i++) {
        if (loggers [i].state != logger_destroyed) {
            delete loggers [i].device;
            loggers [i].state = logger_destroyed;
        }
    }    

    rc = pthread_mutex_destroy (&loggers_mutex);
    assert (rc == 0);

    close (w);
    close (r);
}

void *lcc::api_t::io_routine (void *arg_)
{
    api_t *self = (api_t*) arg_; 
    self->loop ();
    return 0;
}

void lcc::api_t::loop (void)
{
    //  Command sent from api.
    logger_command_t incomming_command;
    
    //  Command status.
    logger_command_status_t status = status_empty;

    //  Poll timeout, default -1.
    int poll_timeout = -1;

    //  Last poll time
//    uint64_t last_poll = 0;

    while (true) {
        //  Wait for in event from api.
        int rc = poll (&pollset, 1, poll_timeout);
        assert (rc != -1);
        
        //  poll timeout
        if (rc == 0) {
            std::cout << "timeout" << std::endl;
            continue;
        }

        //  Read first byte from the socket.
        int bytes = read (r, &incomming_command, sizeof (logger_command_t));
        assert (bytes == sizeof (logger_command_t));
       
        //  Lock loggers_mutex.
        rc = pthread_mutex_lock (&loggers_mutex);
        assert (rc == 0);
    
        //  We should no get here with destroyed logger anf not command_terminate
        if (incomming_command.name != command_terminate && 
              loggers [incomming_command.logger_id].state == logger_destroyed) {

            assert (false); 
        }

        //  Unlock.
        rc = pthread_mutex_unlock (&loggers_mutex);
        assert (rc == 0);

        switch (incomming_command.name) {
            case command_touch:
                //  Send bogus command to logger and read response.
                status = loggers [incomming_command.logger_id].
                    device->touch ();
                break;
            case command_read_service_memory:
                //  Read system memory from logger.
                status = loggers [incomming_command.logger_id].
                    device->read_service_memory ();
                break;
            case command_read_records:
                 //  Read and parse data memory from logger.
                status = loggers [incomming_command.logger_id].
                    device->read_records ();
                break;
            case command_start_online_measure:
                status = loggers [incomming_command.logger_id].
                    device->set_online_interval (incomming_command.size);

                //  Update poll_timeout to keep lowest interval.
                //  Note that in incomming_command.size is interval:)
                if (poll_timeout > (int)incomming_command.size * 1000 || 
                      poll_timeout == -1)
                    poll_timeout = incomming_command.size * 1000;

                break;
            case command_terminate:
                return;
            default:
                assert (false);
        }

        //  Lock loggers_mutex.
        rc = pthread_mutex_lock (&loggers_mutex);
        assert (rc == 0);

        //  Update logger status to ready and update result.
        loggers [incomming_command.logger_id].cmd_status = status;
        loggers [incomming_command.logger_id].state = logger_ready;

        //  Unlock.
        rc = pthread_mutex_unlock (&loggers_mutex);
        assert (rc == 0);

    }
}

void lcc::api_t::send_command (unsigned int logger_id_, 
    logger_command_name_t command_name_, uint16_t addr_, size_t size_)
{

    //  Prepare command.
    logger_command_t command;
    memset (&command, '\0', sizeof (logger_command_t));

    command.logger_id = logger_id_;
    command.name = command_name_;
    command.address = addr_;
    command.size = size_;

    //  If we are not terminating I/O thread mark logger as busy.
    if (command_name_ != command_terminate) {
        //  Lock loggers_mutex.
        int rc = pthread_mutex_lock (&loggers_mutex);
        assert (rc == 0);
        
        //  Check if we have logger in loggers vector.
        assert (logger_exist (logger_id_)); 
               
        //  Check if logger is iddle witn no command and no status.
        assert (loggers [logger_id_].state == logger_iddle); 
        assert (loggers [logger_id_].cmd_name == command_empty); 
        assert (loggers [logger_id_].cmd_status == status_empty); 

        //  Mark logger busy.
        loggers [logger_id_].state = logger_busy;
        loggers [logger_id_].cmd_name = command_name_;
        loggers [logger_id_].cmd_status = status_in_progress;

        //  Unlock mutex
        rc = pthread_mutex_unlock (&loggers_mutex);
        assert (rc ==0);
    }

    //  Send command to I/O thread.        
    ssize_t nbytes = send (w, &command, sizeof (logger_command_t), 0);
    assert (nbytes == sizeof (logger_command_t));
}

int lcc::api_t::create_logger (logger_type_t logger_type_, const char *tty_)
{
   
    logger_t logger;
 
    //  Create proper device object according its type.
    switch (logger_type_) {
        case Rxxxx:
            logger.type = Rxxxx;
            logger.device = new device_Rxxxx_t (tty_);
            logger.state = logger_iddle;
            logger.cmd_name = command_empty;
            logger.cmd_status = status_empty; 
            break;
        default:
            assert (false);
    }

    //  Max 64 loggers.
    assert (loggers.size () < 64);
  
    //  Lock loggers_mutex.
    int rc = pthread_mutex_lock (&loggers_mutex);
    assert (rc == 0);

    //  Push newly created device into the devices vector.
    loggers.push_back (logger);
   
    //  Index in devices vector as logger_id.
    size_t logger_id = loggers.size () - 1;

    //  Unlock mutex.
    rc = pthread_mutex_unlock (&loggers_mutex);
    assert (rc == 0);

    //  Return id.
    return logger_id;
}

void lcc::api_t::destroy_logger (unsigned int logger_id_)
{ 
    //  Lock mutex.
    int rc = pthread_mutex_lock (&loggers_mutex);
    assert (rc == 0);

    //  Can not destroy busy logger.
    assert (loggers [logger_id_].state != logger_busy);

    assert (logger_exist (logger_id_));

    //  Mark logger as destroyed.
    loggers [logger_id_].state = logger_destroyed;

    //  Delete device object.
    delete loggers [logger_id_].device;
    loggers [logger_id_].device = NULL;

    //  Unlock mutex.
    rc = pthread_mutex_unlock (&loggers_mutex);
    assert (rc == 0);
}

void lcc::api_t::touch_logger (unsigned int logger_id_)
{
    send_command (logger_id_, command_touch); 
}

bool lcc::api_t::touch_logger_ok (unsigned int logger_id_)
{
    //  Read command status.
    return is_command_status_ok (logger_id_, command_touch);
}

void lcc::api_t::read_service_memory (unsigned int logger_id_)
{
    send_command (logger_id_, command_read_service_memory);
}

bool lcc::api_t::read_service_memory_ok (unsigned int logger_id_)
{  
    //  Read command status.
    return is_command_status_ok (logger_id_, command_read_service_memory);
}

void lcc::api_t::read_records (unsigned int logger_id_) 
{
    //  Read and parse recorded values.
    return send_command (logger_id_, command_read_records);
}

bool lcc::api_t::read_records_ok (unsigned int logger_id_)
{
    return is_command_status_ok (logger_id_, command_read_records);
}

void lcc::api_t::start_online_measure (unsigned int logger_id_, size_t interval_)
{
    send_command (logger_id_, command_start_online_measure, 0, interval_);
}

bool lcc::api_t::start_online_measure_ok (unsigned int logger_id_)
{
    return is_command_status_ok (logger_id_, command_start_online_measure);
}

bool lcc::api_t::ready (int logger_id_)
{
    //  Lock loggers_mutex.
    int rc = pthread_mutex_lock (&loggers_mutex);
    assert (rc == 0);

    assert (logger_exist (logger_id_));    

    bool is_ready = loggers [logger_id_].state == logger_ready;

    //  Unlock.
    rc = pthread_mutex_unlock (&loggers_mutex);
    assert (rc == 0);

    return is_ready;
}

bool lcc::api_t::is_command_status_ok (unsigned int logger_id_, 
    logger_command_name_t command_name_)
{
    //  Lock loggers_mutex.
    int rc = pthread_mutex_lock (&loggers_mutex);
    assert (rc == 0);

    assert (logger_exist (logger_id_));
    
    //  _ok command have to fits with command.
    assert (loggers [logger_id_].cmd_name == command_name_);

    //  _ok has to ba called only when logger is in ready state.
    assert (loggers [logger_id_].state == logger_ready);
    assert (loggers [logger_id_].cmd_status != status_in_progress);
   
    //  Set logger state to iddle with no command and no status.
    loggers [logger_id_].state = logger_iddle;
    loggers [logger_id_].cmd_name = command_empty;

    bool is_status_ok = loggers [logger_id_].cmd_status == status_ok;

    loggers [logger_id_].cmd_status = status_empty;
 
    //  Unlock.
    rc = pthread_mutex_unlock (&loggers_mutex);
    assert (rc == 0);
   
    return is_status_ok;
}


bool lcc::api_t::get_device_info (unsigned int logger_id_, 
    device_info_t *device_info_)
{
    assert (logger_exist (logger_id_));

    return loggers [logger_id_].device->get_device_info (device_info_);
}

std::list<lcc::record_t> lcc::api_t::get_records (unsigned int logger_id_)
{
    return loggers [logger_id_].device->get_records ();
}

